﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Audio
{
    unsafe public partial class WaveHelper
    {
        #region pcm转A律

        private const int SignBit = 0x80;
        private const int QuantMask = 0xF;
        private const int SegShift = 4;
        private const int SegMask = 0x70;
        private const int Bias = 0x84;
        private static short[] _aLawAndMuLawSegEnd = null;
        private static void CheckALawSegEnd()
        {
            if (_aLawAndMuLawSegEnd == null)
            {
                _aLawAndMuLawSegEnd = new short[] { 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF };
            }
        }

        private static int Search(short val, int size)
        {
            for (int i = 0; i < size; i++)
            {
                if (val <= _aLawAndMuLawSegEnd[i])
                {
                    return i;
                }
            }
            return size;
        }



        /// <summary>
        /// pcm数据文件转换为A律数据文件
        /// </summary>
        /// <param name="pcmFilePath">带头的pcm数据文件路径</param>
        /// <param name="aLawFilePath">A律数据文件存储路径</param>
        public static void PcmToALaw(string pcmFilePath, string aLawFilePath)
        {
            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            if (!File.Exists(pcmFilePath))
            {
                throw new FileNotFoundException("带头的pcm数据文件不存在", pcmFilePath);
            }

            if (string.IsNullOrEmpty(aLawFilePath))
            {
                throw new ArgumentNullException(nameof(aLawFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(pcmFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                var waveInfo = GetWavHead(srcFileStream);
                if (waveInfo.SampleBits != 16)
                {
                    throw new InvalidOperationException($"仅支持16位采样位数的pcm数据文件转换为A律数据文件,当前数据采样位数为{waveInfo.SampleBits}");
                }

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.Pcm);
                var srcDataPtr = new UnmanagedMemoryPointer<short>((srcFileStream.Length - srcFileWaveHeadSize) / sizeof(short));
                disposabler.Add(srcDataPtr, 0);

                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.Cast<byte>().ToSpan());

                var destDataPtr = new UnmanagedMemoryPointer<byte>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitivePcmToALaw(srcDataPtr.DataPtr, destDataPtr.DataPtr, destDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(aLawFilePath);
                var destFileStream = new FileStream(aLawFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.ALaw);
                destFileStream.Write(destDataPtr.GetSpan());

                FillALawAndMuLawHead(destFileStream, WaveEncoding.ALaw, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destFileStream.Length);
            }
        }

        /// <summary>
        /// pcm数据转换为A律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <returns>A律数据</returns>
        public static byte[] PcmToALaw(short[] pcmData)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            byte[] aLawData = new byte[pcmData.Length];
            fixed (byte* aLawDataPtr = aLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToALaw(pcmDataPtr, aLawDataPtr, aLawData.Length);
                }
            }
            return aLawData;
        }

        /// <summary>
        /// pcm数据转换为A律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="aLawData">A律数据</param>
        /// <param name="aLawDataIndex">A律数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void PcmToALaw(short[] pcmData, int pcmDataIndex, byte[] aLawData, int aLawDataIndex, int length)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (aLawData == null)
            {
                throw new ArgumentNullException(nameof(aLawData));
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (aLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(aLawDataIndex), "A律数据不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (aLawDataIndex + length > aLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"A律数据起始索引\"加\"转换数据长度\"大于了\"A律数据\"长度");
            }

            if (pcmDataIndex + length > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* aLawDataPtr = aLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToALaw(pcmDataPtr + pcmDataIndex, aLawDataPtr + aLawDataIndex, length);
                }
            }
        }

        /// <summary>
        /// pcm数据转换为A律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="aLawData">A律数据</param>
        public static void PcmToALaw(Span<short> pcmData, Span<byte> aLawData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* aLawDataPtr = aLawData.DataPtr;
            
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* aLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref aLawData[0]);
#endif
            PrimitivePcmToALaw(pcmDataPtr, aLawDataPtr, Math.Min(pcmData.Length, aLawData.Length));
        }

        /// <summary>
        /// pcm数据转换为A律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="aLawData">A律数据</param>
        public static void PcmToALaw(SpanZ<short> pcmData, SpanZ<byte> aLawData)
        {
            PrimitivePcmToALaw(pcmData.DataPtr, aLawData.DataPtr, Math.Min(pcmData.Length, aLawData.Length));
        }

        private static void PrimitivePcmToALaw(short* pcmDataPtr, byte* aLawDataPtr, int length)
        {
            CheckALawSegEnd();

            int mask;
            int seg;
            byte aVal;
            short pcmVal;

            for (int i = 0; i < length; i++)
            {
                pcmVal = pcmDataPtr[i];

                if (pcmVal >= 0)
                {
                    mask = 0xD5;
                }
                else
                {
                    mask = 0x55;
                    pcmVal = (short)(-pcmVal - 1);
                }

                seg = Search(pcmVal, 8);

                if (seg >= 8)
                {
                    aLawDataPtr[i] = (byte)(0x7F ^ mask);
                }
                else
                {
                    aVal = (byte)(seg << SegShift);
                    if (seg < 2)
                    {
                        aVal |= (byte)((pcmVal >> 4) & QuantMask);
                    }
                    else
                    {
                        aVal |= (byte)((pcmVal >> (seg + 3)) & QuantMask);
                    }
                    aLawDataPtr[i] = (byte)(aVal ^ mask);
                }
            }
        }

        #endregion

        #region A律转pcm

        /// <summary>
        /// A律数据文件转换为pcm数据文件
        /// </summary>
        /// <param name="aLawFilePath">带头的A律数据文件路径</param>
        /// <param name="pcmFilePath">pcm数据文件存储路径</param>
        public static void ALawToPcm(string aLawFilePath, string pcmFilePath)
        {
            if (string.IsNullOrEmpty(aLawFilePath))
            {
                throw new ArgumentNullException(nameof(aLawFilePath));
            }

            if (!File.Exists(aLawFilePath))
            {
                throw new FileNotFoundException("带头的A律数据文件路径", aLawFilePath);
            }

            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(aLawFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.ALaw);
                var srcDataPtr = new UnmanagedMemoryPointer<byte>(srcFileStream.Length - srcFileWaveHeadSize);
                disposabler.Add(srcDataPtr, 0);

                var waveInfo = GetWavHead(srcFileStream);
                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.GetSpan());

                var destDataPtr = new UnmanagedMemoryPointer<short>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitiveALawToPcm(srcDataPtr.DataPtr, destDataPtr.DataPtr, destDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(pcmFilePath);
                var destFileStream = new FileStream(pcmFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.Pcm);
                destFileStream.Write(destDataPtr.Cast<byte>().ToSpan());

                FillPcmWavHead(destFileStream, WaveEncoding.Pcm, DataSampleBits.BIT16, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destFileStream.Length * sizeof(short));
            }
        }

        /// <summary>
        /// A律数据转换为pcm数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <returns>pcm数据</returns>
        public static short[] ALawToPcm(byte[] aLawData)
        {
            if (aLawData == null)
            {
                throw new ArgumentNullException(nameof(aLawData));
            }

            short[] pcmData = new short[aLawData.Length];

            fixed (byte* aLawDataPtr = aLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveALawToPcm(aLawDataPtr, pcmDataPtr, aLawData.Length);
                }
            }

            return pcmData;
        }

        /// <summary>
        /// A律数据转换为pcm数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <param name="aLawDataIndex">A律数据起始索引</param>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void ALawToPcm(byte[] aLawData, int aLawDataIndex, short[] pcmData, int pcmDataIndex, int length)
        {
            if (aLawData == null)
            {
                throw new ArgumentNullException(nameof(aLawData));
            }

            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (aLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(aLawDataIndex), "A律数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (aLawDataIndex + length > aLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"A律数据起始索引\"加\"转换数据长度\"大于了\"A律数据\"长度");
            }

            if (pcmDataIndex + length > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* aLawDataPtr = aLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveALawToPcm(aLawDataPtr + aLawDataIndex, pcmDataPtr + pcmDataIndex, length);
                }
            }

        }

        /// <summary>
        /// A律数据转换为pcm数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void ALawToPcm(Span<byte> aLawData, Span<short> pcmData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* aLawDataPtr = aLawData.DataPtr;
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* aLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref aLawData[0]);
#endif
            PrimitiveALawToPcm(aLawDataPtr, pcmDataPtr, Math.Min(aLawData.Length, pcmData.Length));
        }

        /// <summary>
        /// A律数据转换为pcm数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void ALawToPcm(SpanZ<byte> aLawData, SpanZ<short> pcmData)
        {
            PrimitiveALawToPcm(aLawData.DataPtr, pcmData.DataPtr, Math.Min(aLawData.Length, pcmData.Length));
        }

        private static void PrimitiveALawToPcm(byte* aLawDataPtr, short* pcmDataPtr, int length)
        {
            int seg, temp;
            byte aVal;

            for (int i = 0; i < length; i++)
            {
                aVal = aLawDataPtr[i];
                aVal ^= 0x55;
                temp = (aVal & QuantMask) << 4;
                seg = ((aVal & SegMask) >> SegShift);

                switch (seg)
                {
                    case 0:
                        temp += 8;
                        break;
                    case 1:
                        temp += 0x108;
                        break;
                    default:
                        temp += 0x108;
                        temp <<= seg - 1;
                        break;
                }

                pcmDataPtr[i] = (short)((aVal & SignBit) != 0 ? temp : -temp);
            }
        }

        #endregion
    }
}
